/**
* Created with CLion.
* User:Lenovo
* Date:2023-03-22
* Time:19:09
* Description:动态内存分配
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

struct S
{
    int n;
    char c;
    int arr[];// int arr[0]  柔性数组成员
};

struct S0
{
    int n;
    char c;
    int *arr;
};

void GetMemory00(char **p, int num)
{
    *p = (char *)malloc(num);
}

void Test(void)
{
    char *str = NULL;
    GetMemory00(&str, 100);
    strcpy(str, "hello");
    printf(str);//没有 free
}

void GetMemory(char *p)
{
    p = (char *) malloc(100);
}

int* test()
{
    int a = 10;
    return &a;
}

int *test2()
{
    int *ptr = NULL;
    int a = 10;
    ptr = &a;
    return ptr;
}

char *GetMemory0()
{
    char p[] = "hello world";
    //注意：*p 会被创建在静态区 - 可以返回，没有问题
    return p;
}

int main()
{
    // malloc - void *malloc(size_t size)
    //参数：开辟的空间的字节个数
    //申请一块连续可用的空间，如果申请成功，那么返回一个指向开辟好空间的指针，如果申请空间失败，返回空指针
    //申请 -> 使用 -> 释放(free)
    //释放掉之后要把指针置空
    //如果参数 size 为 0，malloc 的行为是未定义的，取决于编译器
    //空间内部的值是随机值
    {
        int *p = (int *) malloc(20);
        if (p == NULL)
        {
            printf("malloc()--> %s\n", strerror(errno));
            return 1;
        }
        else
        {
            for (int i = 0; i < 5; i++)
            {
                *(p + i) = (i + 1);
            }
            for (int i = 0; i < 5; i++)
            {
                printf("%d ", *(p + i));
            }
            printf("\n");
        }
        free(p);//关闭
        p = NULL;
    }
    // free 用来释放动态开辟的内存
    //如果空间不是动态开辟的，那么 free 的行为未定义
    //如果 free 的参数是空指针，那么什么事都不会发生

    // calloc - void *calloc(size_t num, size_t size)
    //第一个参数：size 的个数    第二个参数：size 的大小  开辟 num 个 size 大小的空间
    //赋值的同时初始化为 0
    //与 malloc 的区别：
    // 1.参数不一样 - calloc 需要两个参数
    // 2.都是在堆区申请内存空间，但是 malloc 不初始化，calloc 会初始化为 0
    {
        int *p = (int *) calloc(10, sizeof(int));
        if (p == NULL)
        {
            printf("calloc()--> %s\n", strerror(errno));
            return 1;
        }
        else
        {
            for (int i = 0; i < 10; i++)
            {
                printf("%d ", *(p + i));
            }
            printf("\n");
        }
        free(p);
        p = NULL;
    }

    // realloc() - void *realloc(void *memblock, size_t size)
    //第一个参数：原空间的地址   第二个参数：重新开辟的空间的大小
    //如果原空间的大小足够，那么在原空间的后面直接增加新的空间
    //如果原空间的大小不足以增加空间：
    // 1.realloc 会寻找更大的空间
    // 2.将原来的数据拷贝到新的空间
    // 3.释放旧的空间
    // 4.返回新空间的地址
    //如果第一个参数是 NULL，那么函数的作用相当于 malloc
    {
        int *p = (int *) malloc(20);
        if (p == NULL)
        {
            printf("%s\n", strerror(errno));
            return 1;
        }
        else
        {
            for (int i = 0; i < 5; ++i)
            {
                p[i] = i + 1;
            }
            int *ptr = realloc(p, 40);//不适合直接使用 p 来接收返回值
            //万一返回空指针，原来的空间地址就再也找不到了
            //替代方案：先用临时指针接受，如果不是空指针再替换
            if (ptr != NULL)
            {
                p = ptr;
                for (int i = 5; i < 10; i++)
                {
                    p[i] = i + 1;
                }
                for (int i = 0; i < 10; i++)
                {
                    printf("%d ", p[i]);
                }
            }
            else
            {
                printf("realloc:%s\n", strerror(errno));
                free(p);
                p = NULL;
                return 1;
            }
        }
        free(p);
        p = NULL;
    }

    // 3.常见的动态内存的错误
    //对空指针的解引用操作
    {
        int *p = (int *) malloc(20);
        for (int i = 0; i < 5; i++)
        {
            p[i] = i;
        }
        free(p);
        p = NULL;
        //可能会出现对 NULL 指针的解引用操作
        //所以 malloc 函数的返回值是需要判断的
    }
    //对动态开辟内存的越界访问
    {
        int *p = (int *) malloc(20);
        if (p == NULL)
        {
            printf("%s\n", strerror(errno));
            return 1;
        }
        /*
        for (int i = 0; i < 10; i++) //越界访问
        {
            p[i] = i;
        }
        */
        free(p);
        p = NULL;
    }
    //对非动态开辟内存的 free 释放
    {
        /*
        int arr[10] = { 0 };
        int *p = arr;
        free(p);
        p = NULL;
        */
    }
    //使用 free 释放一块动态开辟内存的一部分
    {
        int *p = (int *) malloc(40);
        int *tmp = p;
        if (p == NULL)
        {
            printf("%s\n", strerror(errno));
            return 1;
        }
        for (int i = 0; i < 5; i++)
        {
            *p++ = i + 1;
        }
        free(p);
        p = NULL;
        //释放了但没有完全释放
        free(tmp);
        tmp = NULL;
    }
    //对同一块动态内存多次释放
    {
        int *p = (int *) malloc(20);
        if (p == NULL)
        {
            return 1;
        }
        free(p);
        /* free(p); */ // err
        p = NULL;
        free(p);
        p = NULL;//这样可以
    }
    //动态开辟内存忘记释放
    // malloc calloc realloc 申请的空间如果不想使用，需要 free 释放
    //如果不使用 free 释放，程序结束之后，也会由操作系统回收
    //如果不使用 free 释放，程序也不结束 - 内存泄漏
    {
        /*
        while (1)
        {
            malloc(20);
        }
        */
        //小心死机
    }
    //自己申请的空间，尽量自己释放
    //自己不释放的，要告诉别人来释放

    //笔试题
    // 1.
    {
        /*
        char *str = NULL;
        GetMemory(str);
        strcpy(str, "hello world");
        printf(str);
        */
        // 1.调用 GetMemory 函数的时候，str 的传参为值传递，p 是 str 的临时拷贝，所以在 GetMemory 函数内部动态开辟空间的地址存放在 p 中的时候，不会影响 str，所以 GetMemory 函数返回之后，str 中依然是 NULL 指针，strcpy 函数就会调用失败，原因是对 NULL 的解引用操作，程序会崩溃
        // 2.GetMemory 函数内部 malloc 申请的空间没有机会释放，造成了内存泄漏
    }
    // 2.
    {
        char *str = NULL;
        str = GetMemory0();
        printf(str, "\n");//返回空指针，什么都不打印(CLion 中)
        /* printf("%d\n", *test()); */ // error
        printf("%d\n", *test2());
        //返回栈空间地址的问题
        // GetMemory 函数内部创建的数组是临时的，虽然返回了数组的起始地址给了 str，但是数组的内存除了 GetMemory 函数就被回收了
        //而 str 依然保存了数组的起始地址，这时候如果使用 str，str 就是野指针
    }

    // 3.
    {
        Test();
        //以二级指针传参，能够返回正确的地址
    }

    // 4.
    {
        char *str = (char *) malloc(100);
        strcpy(str, "hello");
        free(str);
        /*
        if(str != NULL)
        {
            strcpy(str, "world");
            printf(str);
        }
        */
        //访问取消分配的内存空间
    }

    // C / C++ 程序的内存开辟
    //内核空间：操作系统使用，用户代码不能读写
    //栈区(向下增长)
    //内存映射段
    //堆区(向上增长)
    //数据段(静态区)：全局变量，静态数据
    //代码段：可执行代码，只读常量(串池)

    //柔性数组
    // C99 中，结构中的最后一个元素允许是未知大小的数组，叫做柔性数组成员
    // ps：
    // 结构中的柔性数组成员前面至少有一个其他成员
    // sizeof 返回的这种结构的大小不包括柔性数组的内存

    // 方案一：柔性数组
    {
        printf("%zd\n", sizeof(struct S));
        //包含柔性数组成员的结构用 malloc() 函数进行内存的动态分配，并且分配的内存大小应该大于结构的大小，以适应柔性数组的预期大小
        struct S *ps = (struct S *) malloc(sizeof(struct S) + 10 * sizeof(int));//想要放 10 个整形
        if (ps == NULL) {
            printf("%s\n", strerror(errno));
            return 1;
        }
        // arr 空间里面有十个整形
        for (int i = 0; i < 10; i++) {
            ps->arr[i] = i;
        }
        for (int i = 0; i < 10; i++) {
            printf("%d ", ps->arr[i]);
        }
        printf("\n");

        struct S *ptr = realloc(ps, sizeof(struct S) + 20 * sizeof(int));
        if (ptr == NULL) {
            printf("%s", strerror(errno));
            return 1;
        }
        ps = ptr;

        free(ps);
        ps = NULL;
        //柔性：体现在分配的内存空间可变上
    }
    // 方案二：结构体中的整形指针
    {
        struct S0 *ps0 = (struct S0 *) malloc(sizeof(struct S0));
        if (ps0 == NULL)
        {
            perror("malloc");
            return 1;
        }
        int *ptr = (int *) malloc(sizeof(int) * 10);
        if (ptr == NULL)
        {
            perror("malloc2");
            return 1;
        }
        else
        {
            ps0 -> arr = ptr;
        }
        for (int i = 0; i < 10; i++)
        {
            ps0 -> arr[i] = i;
        }
        for (int i = 0; i < 10; i++)
        {
            printf("%d ", ps0 -> arr[i]);
        }
        ptr = (int *) realloc(ps0 -> arr, sizeof(int) * 20);
        if (ptr == NULL)
        {
            perror("realloc");
            return 1;
        }
        ps0 -> arr = ptr;
        free(ps0 -> arr);
        ps0 -> arr = NULL;
        free(ps0);
        ps0 = NULL;
    }
    // 对比：
    // 方案一：malloc 一次，free 一次，容易维护空间，不易出错
        // malloc 次数少，内存碎片就会较少，内存的使用率就会高一些
    // 方案二：malloc 两次，free 两次，维护难度加大，容易出错
        // malloc 次数多，内存碎片就会增多，内存的使用率就下降了



    return 0;
}